package cn.itcast.hotel.service.impl;

import cn.itcast.hotel.common.PageResult;
import cn.itcast.hotel.dto.HotelPageQueryDTO;
import cn.itcast.hotel.mapper.HotelMapper;
import cn.itcast.hotel.pojo.Hotel;
import cn.itcast.hotel.service.HotelService;
import cn.itcast.hotel.vo.HotelVO;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.lucene.search.function.CombineFunction;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 酒店服务
 *
 * @author liudo
 * @date 2023/08/15
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class HotelServiceImpl extends ServiceImpl<HotelMapper, Hotel> implements HotelService {

    private final RestHighLevelClient restHighLevelClient;

    @Override
    public PageResult<HotelVO> listByQuery(HotelPageQueryDTO hotelPageQueryDTO) {
        try {
            SearchRequest searchRequest = new SearchRequest("hotel");
            // 构建查询条件
            BoolQueryBuilder boolQuery = getBoolQuery(hotelPageQueryDTO);

            // 构建广告算分条件
            // 1. 原始查询 2. 过滤广告的查询 3.分 4.加权条件
            searchRequest.source().query(
                    QueryBuilders.functionScoreQuery(
                                    boolQuery,
                                    new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                                            new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                                    QueryBuilders.termQuery("isAD", true),
                                                    ScoreFunctionBuilders.weightFactorFunction(10F)
                                            )
                                    }

                            )
                            .boostMode(CombineFunction.SUM)
            );
            // 构建排序条件
            if (StringUtils.isNoneBlank(hotelPageQueryDTO.getLocation())) {
                searchRequest.source()
                        .sort(
                                SortBuilders.geoDistanceSort("location", new GeoPoint(hotelPageQueryDTO.getLocation()))
                                        .order(SortOrder.ASC)
                                        .unit(DistanceUnit.KILOMETERS)
                        );
            }
            // 构建分页条件
            int size = Objects.isNull(hotelPageQueryDTO.getSize()) ? 20 : hotelPageQueryDTO.getSize();
            int page = Objects.isNull(hotelPageQueryDTO.getPage()) ? 1 : hotelPageQueryDTO.getPage();
            searchRequest.source()
                    .from((page - 1) * size)
                    .size(size);
            SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            // 结果的解析
            SearchHits hits = response.getHits();
            long total = hits.getTotalHits().value;
            SearchHit[] hitsHits = hits.getHits();
            // map 用来类型转换
            List<HotelVO> hotelVOS = Arrays.stream(hitsHits)
                    .map(item -> {
                        String sourceAsString = item.getSourceAsString();
                        HotelVO hotelVO = JSON.parseObject(sourceAsString, HotelVO.class);
                        Object[] sortValues = item.getSortValues();
                        if (sortValues.length > 0) {
                            Object value = sortValues[0];
                            hotelVO.setDistance(value);
                        }
                        return hotelVO;
                    })
                    .collect(Collectors.toList());

            PageResult<HotelVO> pageResult = new PageResult<>();
            pageResult.setTotal(total);
            pageResult.setData(hotelVOS);
            return pageResult;
        } catch (IOException e) {
            log.error("搜索失败", e);
        }
        return new PageResult<>();
    }

    @Override
    public Map<String, List<String>> filters(HotelPageQueryDTO hotelPageQueryDTO) {
        final String brandAgg = "brandAgg";
        final String cityAgg = "cityAgg";
        final String starNameAgg = "starNameAgg";
        try {
            SearchRequest searchRequest = new SearchRequest("hotel");
            // 构建查询条件
            BoolQueryBuilder boolQuery = getBoolQuery(hotelPageQueryDTO);
            searchRequest.source().query(boolQuery);
            // 构建分页条件
            searchRequest.source().size(0);
            // 构建聚合条件
            searchRequest.source()
                    .aggregation(
                            AggregationBuilders.terms(brandAgg)
                                    .field("brand")
                                    .size(10)
                    )
                    .aggregation(
                            AggregationBuilders.terms(cityAgg)
                                    .field("city")
                                    .size(10)
                    )
                    .aggregation(
                            AggregationBuilders.terms(starNameAgg)
                                    .field("starName")
                                    .size(10)
                    );
            SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            Aggregations aggregations = response.getAggregations();

            ParsedStringTerms aggregationBrand = aggregations.get(brandAgg);
            ParsedStringTerms aggregationCity = aggregations.get(cityAgg);
            ParsedStringTerms aggregationStarName = aggregations.get(starNameAgg);
            List<? extends Terms.Bucket> brandBuckets = aggregationBrand.getBuckets();
            List<? extends Terms.Bucket> cityBuckets = aggregationCity.getBuckets();
            List<? extends Terms.Bucket> starNameBuckets = aggregationStarName.getBuckets();

            List<String> brandList = brandBuckets.stream()
                    .map(MultiBucketsAggregation.Bucket::getKeyAsString)
                    .collect(Collectors.toList());

            List<String> cityList = cityBuckets.stream()
                    .map(MultiBucketsAggregation.Bucket::getKeyAsString)
                    .collect(Collectors.toList());

            List<String> starNameList = starNameBuckets.stream()
                    .map(MultiBucketsAggregation.Bucket::getKeyAsString)
                    .collect(Collectors.toList());
            //  {"品牌": [], "城市": [], "星级": []}
            HashMap<String, List<String>> hashMap = new HashMap<>(8);
            hashMap.put("brand", brandList);
            hashMap.put("city", cityList);
            hashMap.put("starName", starNameList);
            return hashMap;
        } catch (IOException e) {
            log.error("聚合失败", e);
        }
        return new HashMap<>(0);
    }

    @Override
    public List<String> suggestion(String key) {
        final String suggestName = "suggestWord";
        try {
            SearchRequest searchRequest = new SearchRequest("hotel");
            // 构建建议条件
            searchRequest.source()
                    .suggest(
                            new SuggestBuilder()
                                    .addSuggestion(suggestName,
                                            SuggestBuilders.completionSuggestion("suggestion")
                                                    .skipDuplicates(true)
                                                    .prefix(key)
                                                    .size(10)
                                    )
                    );
            SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

            CompletionSuggestion suggestion = response.getSuggest().getSuggestion(suggestName);
            List<CompletionSuggestion.Entry.Option> options = suggestion.getOptions();

            return options.stream()
                    .map(Suggest.Suggestion.Entry.Option::getText)
                    .map(Text::string)
                    .collect(Collectors.toList());
        } catch (IOException e) {
            log.error("补全错误", e);
        }
        return Collections.emptyList();
    }

    /**
     * 获取布尔查询条件
     *
     * @param hotelPageQueryDTO 酒店页面查询d去
     * @return {@link BoolQueryBuilder}
     */
    private static BoolQueryBuilder getBoolQuery(HotelPageQueryDTO hotelPageQueryDTO) {
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        if (StringUtils.isBlank(hotelPageQueryDTO.getKey())) {
            boolQuery.must(
                    QueryBuilders.matchAllQuery()
            );
        } else {
            boolQuery.must(
                    QueryBuilders.matchQuery("all", hotelPageQueryDTO.getKey())
            );
        }
        // 城市
        if (StringUtils.isNoneBlank(hotelPageQueryDTO.getCity())) {
            boolQuery.filter(
                    QueryBuilders.termQuery("city", hotelPageQueryDTO.getCity())
            );
        }
        // 星级
        if (StringUtils.isNoneBlank(hotelPageQueryDTO.getStarName())) {
            boolQuery.filter(
                    QueryBuilders.termQuery("starName", hotelPageQueryDTO.getStarName())
            );
        }
        // 品牌
        if (StringUtils.isNoneBlank(hotelPageQueryDTO.getBrand())) {
            boolQuery.filter(
                    QueryBuilders.termQuery("brand", hotelPageQueryDTO.getBrand())
            );
        }
        // 价格
        if (Objects.nonNull(hotelPageQueryDTO.getMinPrice()) &&
                Objects.nonNull(hotelPageQueryDTO.getMaxPrice())) {
            boolQuery.filter(
                    QueryBuilders.rangeQuery("price")
                            .lt(hotelPageQueryDTO.getMaxPrice())
                            .gt(hotelPageQueryDTO.getMinPrice())
            );
        }
        return boolQuery;
    }
}
